`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer:       James Forrest
// 
// Create Date:    14:45:07 08/20/2013 
// Design Name: 
// Module Name:    coprocessor0 
// Project Name: 
// Target Devices: 
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//////////////////////////////////////////////////////////////////////////////////
module coprocessor0(
    input         clk, reset,
    input  [15:0] interrupts,
    input   [7:0] writeAddress,      // {selM, rdM} (selE = immE[2:0])
    input         writeEnable,       // cp0WriteEnableW
    input  [31:0] writeData,         // ResultW
    input  [31:0] ALUOutM,           // the data mem address
    input  [31:0] pcF, pcD, pcE, pcM,// PC of each possibly incomplete stage
    input         memwriteM, dataAckM,
    input   [1:0] jumpD,             // and the signals to determine which is first incomplete
    input         regwriteD, branchD, flushD, flushE, flushM,
    input         eret,              // eretM
    input         overflowAble,      // M
    input         overflow,          // M
    input         addressErrorOnLoadAble,  // M
    input         addressErrorOnStoreAble, // M
    input         addressErrorInstr, // instrInvalidAddressM
    input         addressErrorData,  // dataInvalidAddressM
    input         excBadInstruction, // M
    input   [7:0] readAddress,       // {selM, rdM} (selE = immE[2:0])
    output [31:0] readData,          // M, also used as jump address
    output        cp0Jump            // FD (cp0Jump)
    );

    // declare internal wires to the coprocessor
    wire        exceptionLevel, compareEnable, statusEnable, causeEnable, ebaseEnable, interruptEnable, notInterruptEnable;
    wire  [1:0] softInterrupts;
    wire  [2:0] readIndex0, readIndex, writeIndex;
    wire  [4:0] excCode0, excCode1, excCode2, excCode3, excCode4, exceptionCode;
    wire [31:0] count, compare, EBase, EPC, badVAddrData, badVAddr, pcIncomplete;
    wire [17:0] interruptMask, notInterruptMask; // 16 hardware, 2 software
    wire [18:0] maskedInterrupts; // 1 timer, 16 hardware, 2 software

    // up-front logic simplification
    assign excOverflow = overflow & overflowAble;
    // decode to work out what the address error is
    // addressErrorOnLoadAble should only be high for lw, we don't need a signal for loading from imem, if it doesn't work it's always bad
    // addressErrorI is always going to be a load error, store is only for dmem
    assign excAddressErrorOnLoadData = addressErrorData & addressErrorOnLoadAble;
    assign excAddressErrorOnLoadInstr = addressErrorInstr;
    assign excAddressErrorOnStore = addressErrorData & addressErrorOnStoreAble;

    // Note: MARS does not support using select in assembly, using 15 here will be the same as 15.1 as a workaround
    // register      select register
    // r8   : badVAddr (000 01)000
    // r9   : count    (000 01)001
    // r11  : compare  (000 01)011
    // r12  : status   (000 01)100
    // r13  : cause    (000 01)101
    // r14  : EPC      (000 01)110
    // r15.1: EBase    (001 01)111
    // if these are the only registers, can just use the last three bits as the index
    // except for write to epc (enabled by interrupt signals only).
    // on eret, registerAddress is epc (110)
    // on interrupt, set to ebase (111)
    //   note: interrupt doesn't occur if blocked in interrupt mask or by interrupt enable, or if exception level is set
    mux2 #(3) readIMux0(readAddress[2:0], 3'b111, cp0Jump, readIndex0);
    mux2 #(3) readIMux(readIndex0, 3'b110, eret, readIndex);

    assign writeIndex = writeAddress[2:0];

    // BadVAddr: writes on exception, along with EPC, reads normally
    // writes either the dmem address or the imem address, depending on the source of the exception
    // data mem takes precedence as the F stage is 'later'
    mux2    #(32) badVAddrMux(ALUOutM, pcF, excAddressErrorOnLoadData | excAddressErrorOnStore, badVAddrData);
    flopenr #(32) badVAddrFlop(~clk, reset, cp0Jump, badVAddrData, badVAddr);

    // Count:
    flopr #(32) countReg(clk, reset, count+1, count);
    // Compare: read and write normally
    flopenr #(32) compReg(clk, reset, compareEnable, writeData, compare);

    // Status: interrupt mask(25:8), exception level bit(1), global interrupt enable(0)
    //     interrupt mask is software writable, turns interrupts on and off
    // (store them as inverse so it resets to all on)
    flopenr #(18) maskReg(~clk, reset, statusEnable, ~writeData[25:8], notInterruptMask[17:0]);
    assign interruptMask = ~notInterruptMask;
    //     exception level bit is on for handling routines, turned off by eret (it blocks more interrupts)
    assign #1 exceptionLevel = ((|maskedInterrupts & interruptEnable) | (|exceptionCode) | exceptionLevel) & ~(reset | eret);
    //     global interrupt enable turns on or off all interrupts, overrules the interrupt mask, overruled by exception level
    flopenr #(1) ieReg(~clk, reset, statusEnable, ~writeData[0], notInterruptEnable);
    assign interruptEnable = ~notInterruptEnable;
    //     wire them together
    wire [31:0] status = {6'd0, interruptMask, 6'd0, exceptionLevel, interruptEnable};

    // Cause: made up of external interrupts(25:10), timer(30), software(9:8), and the exception code(6:2)
    //     create registers to hold the software interrupt signals
    assign timer = count >= compare & compare != 32'd0;
    flopenr #(2) softIReg(~clk, reset, causeEnable, writeData[9:8], softInterrupts);

    //     set up the exception decoder
    // exception codes are defined in the MIPS spec, these come from See MIPS Run
    // prioritise exceptions, some exceptions will cause other exceptions, get to the root
    mux2 #(5) excCodeMux0(    5'd0,  5'd5, excAddressErrorOnStore    , excCode0); // Only one of these two
    mux2 #(5) excCodeMux1(excCode0,  5'd4, excAddressErrorOnLoadData , excCode1); // will happen at a time
    mux2 #(5) excCodeMux2(excCode1, 5'd12, excOverflow               , excCode2); // Could cause data address error
    mux2 #(5) excCodeMux3(excCode2, 5'd10, excBadInstruction         , excCode3); // Could cause overflow/data address error
    mux2 #(5) excCodeMux4(excCode3,  5'd4, excAddressErrorOnLoadInstr, excCode4); // Can't even load an instruction
    // don't change until the processor has had a chance to handle any previous exception
    flopenr #(5) excCodeReg(clk, reset, ~exceptionLevel, excCode4, exceptionCode);

    //     wire them together
    wire [31:0] cause = {1'd0, timer, 4'd0, interrupts, softInterrupts, 1'd0, exceptionCode, 2'd0};

    // EPC: write the pc when jumping
    flopenr #(32) epcReg(clk, reset, cp0Jump, pcIncomplete, EPC);

    // EBase: write to ebase like normal, unset by default
    flopenr #(32) ebaseReg(~clk, reset, ebaseEnable, writeData, EBase);

    // have any interrupts happened that we want to know about?
    assign maskedInterrupts = {timer, {interrupts, softInterrupts} & interruptMask};

    // has exceptionLevel changed in this cycle?
    flopr #(1) excLevelReg(clk, reset, exceptionLevel, startExcLevel);
    assign #1 cp0Jump = startExcLevel ^ exceptionLevel;

    // set up mux to set the read data to the correct register, use a negedge clocked flop to outlast the pipeline flush
    mux8 #(32) readMux(badVAddr, count, 32'd0, 32'd0, status, cause, EPC, EBase, readIndex, readData);

    // write to some registers, on negedge
    assign #1 compareEnable = writeEnable & writeIndex==3'b011;
    assign #1  statusEnable = writeEnable & writeIndex==3'b100;
    assign #1   causeEnable = writeEnable & writeIndex==3'b101;
    assign #1   ebaseEnable = writeEnable & writeIndex==3'b111;

    /*
    instruction completion per stage:

    F:  none
    D:  j*(~jal[r]), b*
    E:  none
    M:  sw, eret (irrelevant)
    W:  R-type(~jr), jal[r], I-type(~b*, *w), mfc0, mtc0, lw
    */
    reg [2:0] complete; // D, E, M
    always @(posedge clk) begin
        if (reset) complete <= #1 0;
        else begin
            // M stage completion
            complete[0] <= #1 complete[1] | flushM | (memwriteM & dataAckM);
            // E stage completion
            complete[1] <= #1 complete[2] | flushE;
            // D stage completion
            complete[2] <= #1 flushD | branchD | (|jumpD & ~regwriteD);
        end
    end
    // work backwards
    assign #1 pcIncomplete = ~complete[0] ? pcM : ~complete[1] ? pcE : ~complete[2] ? pcD : pcF;

endmodule
